package com.vclee.fast.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.vclee.fast.utils.exception.Assert;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class CipherUtils {
    static {
        //如果是PKCS7Padding填充方式，则必须加上下面这行(为保证前后端一致性建议使用PKCS7Padding方式，也可以使用PKCS5Padding则可以不加)
        Security.addProvider(new BouncyCastleProvider());
    }


    /**
     * 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序（字典序），并且生成(name=123&age=18&hobby=打球)格式(即url参数串)
     *
     * @param paraMap    要排序的Map对象(map对象里面属性和值都不能含有&特殊字符否则url参数串会解析错误)
     * @param urlEncode  是否需要URLENCODE(选择true会将空格和中文转换为含有%的东西)
     * @param keyToLower 是否需要将Key转换为全小写(true:key转化成小写，false:不转化)
     * @return url参数串
     */
    public static String formatUrlMap(Map<String, Object> paraMap, boolean urlEncode, boolean keyToLower) {
        String buff = "";
        try {
            List<Map.Entry<String, Object>> infoIds = new ArrayList<>(paraMap.entrySet());
            // 对所有传入参数按照字段名的 ASCII 码从小到大排序（字典序）
            infoIds.sort(Map.Entry.comparingByKey());
            // 构造URL 键值对的格式
            StringBuilder buf = new StringBuilder();
            for (Map.Entry<String, Object> item : infoIds) {
                if (!Assert.isBlank(item.getKey())) {
                    String key = item.getKey();
                    Object val = item.getValue();
                    if (urlEncode) {
                        val = URLEncoder.encode(val.toString(), "utf-8");
                    }
                    if (keyToLower) {
                        buf.append(key.toLowerCase()).append("=").append(val);
                    } else {
                        buf.append(key).append("=").append(val);
                    }
                    buf.append("&");
                }
            }
            buff = buf.toString();
            if (!Assert.isEmpty(buff)) {
                buff = buff.substring(0, buff.length() - 1);
            }
        } catch (Exception e) {
            return null;
        }
        return buff;
    }


    /**
     * 进行MD5加密
     * @return String 加密后的字符串
     */
    public static String MD5(String pwd) {
        char[] md5String = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};//用于加密的字符
        try {
            byte[] btInput = pwd.getBytes(StandardCharsets.UTF_8);
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = md5String[byte0 >>> 4 & 0xf];
                str[k++] = md5String[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            return null;
        }
    }

    /***********************************************DES加解密************************************************************************/

    /**
     * 加密字符串
     *
     * @param strIn 需加密的字符串
     * @return 加密后的字符串
     */
    public static String desEncrypt(String strIn, String strKey) {
        try {
            Key key = getKey(strKey.getBytes());
            Cipher encryptCipher = Cipher.getInstance("DES");
            encryptCipher.init(Cipher.ENCRYPT_MODE, key);
            return byteArr2HexStr(encryptCipher.doFinal(strIn.getBytes()));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 解密字符串
     *
     * @param strIn 需解密的字符串
     * @return 解密后的字符串
     */
    public static String desDecrypt(String strIn, String strKey) {
        try {
            Key key = getKey(strKey.getBytes());
            Cipher decryptCipher = Cipher.getInstance("DES");
            decryptCipher.init(Cipher.DECRYPT_MODE, key);
            return new String(decryptCipher.doFinal(hexStr2ByteArr(strIn)));
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 将byte数组转换为表示16进制值的字符串， 如：byte[]{8,18}转换为：0813，
     * 与public static byte[]hexStr2ByteArr(String strIn) 互为可逆的转换过程
     *
     * @param arrB 需要转换的byte数组
     * @return 转换后的字符串
     */
    private static String byteArr2HexStr(byte[] arrB) {
        int iLen = arrB.length;
        // 每个byte用2个字符才能表示，所以字符串的长度是数组长度的2倍
        StringBuilder sb = new StringBuilder(iLen * 2);
        for (int b : arrB) {
            int intTmp = b;
            while (intTmp < 0) intTmp = intTmp + 256;// 把负数转换为正数
            if (intTmp < 16) sb.append("0");// 小于0F的数需要在前面补0
            sb.append(Integer.toString(intTmp, 16));
        }
        return sb.toString();
    }

    /**
     * 将表示16进制值的字符串转换为byte数组
     * 与public static String byteArr2HexStr(byte[] arrB) 互为可逆的转换过程
     *
     * @param strIn 需要转换的字符串
     * @return 转换后的byte数组
     */
    private static byte[] hexStr2ByteArr(String strIn) {
        byte[] arrB = strIn.getBytes();
        int iLen = arrB.length;
        // 两个字符表示一个字节，所以字节数组长度是字符串长度除以2
        byte[] arrOut = new byte[iLen / 2];
        for (int i = 0; i < iLen; i = i + 2) {
            String strTmp = new String(arrB, i, 2);
            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
        }
        return arrOut;
    }

    /**
     * 从指定字符串生成密钥，密钥所需的字节数组长度为8位 不足8位时后面补0，超出8位只取前8位
     *
     * @param arrBTmp 构成该字符串的字节数组
     * @return 生成的密钥
     */
    private static Key getKey(byte[] arrBTmp) {
        byte[] arrB = new byte[8];
        for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) arrB[i] = arrBTmp[i];
        return new javax.crypto.spec.SecretKeySpec(arrB, "DES");
    }

    /*********************************************AES加解密*************************************************************************/

    public static String saltEncrypt(String content,String password){
        return aesEncrypt( new Double((Math.random() * 9 + 1) * 100000).intValue()+content,password);
    }

    public static String saltDecrypt(String content,String password){
        return Objects.requireNonNull(aesDecrypt(content,password)).substring(6);
    }

    public static String asaInitKey(){
        String str="yzABcdCfgDqrEvwFGpsHIoxJKtuLhiMejNOklPQmnRSabTUX01VW23456YZ789";
        Random random=new Random();
        StringBuilder sb=new StringBuilder();
        while (sb.length()<16) sb.append(str.charAt(random.nextInt(str.length())));
        return sb.toString();
    }

    public static String aesEncrypt(String content,String password){
        try {
            SecretKeySpec sKeySpec = new SecretKeySpec(password.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
            cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
            return encodeHexString(cipher.doFinal(content.getBytes()));
        } catch (Exception ex) {
            return null;
        }
    }

    public static String aesDecrypt(String content,String password){
        try {
            SecretKeySpec sKeySpec = new SecretKeySpec(password.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
            return new String(cipher.doFinal(decodeHex(content.toCharArray())));
        } catch (Exception ex) {
            return null;
        }
    }

    private static final char[] DIGITS_LOWER = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    private static byte[] decodeHex(char[] data) throws Exception {
        int len = data.length;
        if ((len & 1) != 0) {
            throw new Exception("Odd number of characters.");
        } else {
            byte[] out = new byte[len >> 1];
            int i = 0;

            for(int j = 0; j < len; ++i) {
                int f = toDigit(data[j], j) << 4;
                ++j;
                f |= toDigit(data[j], j);
                ++j;
                out[i] = (byte)(f & 255);
            }

            return out;
        }
    }
    private static int toDigit(char ch, int index) throws Exception {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new Exception("Illegal hexadecimal character " + ch + " at index " + index);
        } else {
            return digit;
        }
    }


    private static String encodeHexString(byte[] data) {
        char[] toDigits = DIGITS_LOWER;
        int l = data.length;
        char[] out = new char[l << 1];
        int i = 0;

        for(int var5 = 0; i < l; ++i) {
            out[var5++] = toDigits[(240 & data[i]) >>> 4];
            out[var5++] = toDigits[15 & data[i]];
        }
        return new String(out);
    }

    /*********************************************RSA加解密*************************************************************************/

    /**
     * 生成公钥与私钥
     *
     * @return privateKey publicKey
     */
    public static Map<String, String> rsaKeys(){
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(1024, new SecureRandom());
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
            Base64.Encoder base64Encoder = Base64.getEncoder();
            Map<String, String> json = new HashMap<>();
            json.put("publicKey", new String(base64Encoder.encode(publicKey.getEncoded())));
            json.put("privateKey", new String(base64Encoder.encode((privateKey.getEncoded()))));
            return json;
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    /**
     * RSA 公钥加密
     *
     * @param content   待加密字符串
     * @param publicKey 公钥
     * @return 解密结果
     */
    public static String rsaEncrypt(String content,String publicKey){
        try {
            byte[] decoded = Base64.getDecoder().decode(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            return new String(Base64.getEncoder().encode(cipher.doFinal(content.getBytes())));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * RSA 私钥解密
     *
     * @param content    待解密字符串
     * @param privateKey 私钥
     * @return 解密参数
     */
    public static String rsaDecrypt(String content,String privateKey){
        try {
            byte[] inputByte = Base64.getDecoder().decode(content.getBytes());
            byte[] decoded = Base64.getDecoder().decode(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            return new String(cipher.doFinal(inputByte));
        } catch (Exception e) {
            return null;
        }
    }
}
